Plasma only samples
import pandas as pd
samples = pd.read_csv('data/sample_sheet.csv')
# must be Healthy Control Study and must pass MiSeq QC
samples = samples.loc[(samples['Study'] == 'Healthy Controls') & (samples['MISEQ.QC.PASS'] == 'PASS')]
samples = samples.set_index('MT.Unique.ID').sort_values(by=['Participant.ID', 'Source'])
# capitalize & strip whitespace for consistency
for column in ['Gender', 'Race', 'Source']:
samples[column] = samples[column].str.capitalize()
samples[column] = samples[column].str.strip()
# use correct ontology terms
race_ontology = {'Asian': 'Asian',
'Black or african american': 'African_American',
'Mixed/asian & white': 'Multiracial',
'Mixed/asian &black': 'Multiracial',
'Mixed/black, white, asian': 'Multiracial',
'Native hawiian or other pacific islander': 'Pacific_Islander',
'Pacific islander': 'Pacific_Islander',
'White': 'White'}
for id in samples.index:
race = samples.at[id, 'Race']
samples.at[id, 'Race'] = race_ontology[race] if race in race_ontology else 'Multiracial'
# get only the plasma samples
mir_counts = pd.read_csv("data/get_canonical/canon_mir_counts.csv", index_col=0)
samples.index = pd.Index(['X' + str(row) for row in samples.index])
plasma_samples = samples.loc[samples['Source'] == "Plasma"]
plasma_mir_counts = mir_counts[plasma_samples.index]
plasma_samples.to_csv('data/plasma_samples.csv')
plasma_mir_counts.to_csv('data/plasma_mir_counts.csv')
Load the sample data and miRNA counts
samples <- read.csv('data/plasma_samples.csv')
counts <- read.csv('data/plasma_mir_counts.csv')
samples <- subset(samples, Library.Generation.Set != "SetRecheck" ) # exclude SetRecheck samples
samples <- samples[samples$X != "X11" & samples$X != "X92",] # remove outliers
samples$Participant.ID <- factor(samples$Participant.ID) # ID is categorical, not numerical
rownames(counts) = counts$X
rownames(samples) = samples$X
counts$X <- NULL # remove extra column
samples$X <- NULL
counts <- counts[,unique(rownames(samples))]
head(samples)
head(counts)
Filter
library(edgeR)
design <- model.matrix(~0+Race+Gender+Age, samples)
dge = DGEList(counts = counts, samples = samples)
# require miRNAs to have CPM > 1 in at least 2 samples
countsPerMillion <- edgeR::cpm(dge)
countCheck <- countsPerMillion > 1
head(countCheck)
keep <- which(rowSums(countCheck) >= 2)
dge <- dge[keep,]
Explore variance
library(SingleCellExperiment)
library(scater)
Attaching package: ‘scater’
The following object is masked from ‘package:EDASeq’:
plotRLE
The following object is masked from ‘package:S4Vectors’:
rename
The following object is masked from ‘package:limma’:
plotMDS
The following object is masked from ‘package:stats’:
filter
reads_sce <- SingleCellExperiment(assays=list(counts=dge$counts), colData=dge$samples)
# remove unexpressed miRNAs
keep_feature <- rowSums(counts(reads_sce) > 0) > 0
reads_sce <- reads_sce[keep_feature, ]
reads_sce <- calculateQCMetrics(reads_sce)
Note that the names of some metrics have changed, see 'Renamed metrics' in ?calculateQCMetrics.
Old names are currently maintained for back-compatibility, but may be removed in future releases.
# log transform
cpm(reads_sce) <- calculateCPM(reads_sce)
reads_sce <- normalize(reads_sce)
using library sizes as size factors
logcounts(reads_sce) <- log2(calculateCPM(reads_sce) + 1)
# visualize
hist(reads_sce$total_counts, breaks=100) # counts per sample

hist(reads_sce$total_features, breaks=100) # counts per miRNA

plotQC(reads_sce, type = "highest-expression")

plotQC(reads_sce, type="explanatory-variables", variables=c("Index", "Participant.ID", "Collection.Date", "Library.Generation.Set", "MiSeq.QC.Run", 'total_features', "Age", "Race", "Gender"))

Examine sources of variation
plotPCA(reads_sce, exprs_values = "logcounts", colour_by = "Library.Generation.Set", size_by = "total_features")
non-plotting arguments like 'exprs_values' should go in 'run_args'

plotPCA(reads_sce, exprs_values = "logcounts", colour_by = "Race", shape_by="Gender", size_by = "total_features")
non-plotting arguments like 'exprs_values' should go in 'run_args'

for (var in c("total_features", "Age", "Library.Generation.Set", "Index", "Participant.ID", "Race", "Gender", "Collection.Date")) {
print(
plotQC(reads_sce, type = "find-pcs", exprs_values = "logcounts", variable = var)
) + ggtitle(var)
}








Normalize & Remove unwanted sources of variation
library(RUVSeq)
library(ggplot2)
library(mvoutlier)
Loading required package: sgeostat
sROC 0.1-2 loaded
dge <- calcNormFactors(dge)
dge <- estimateGLMCommonDisp(dge, design)
dge <- estimateGLMTagwiseDisp(dge, design)
fit <- glmFit(dge, design)
res <- residuals(fit, type="deviance")
set <- newSeqExpressionSet(dge$counts, phenoData=dge$samples)
ruvr_sets <- list()
for(k in 1:5) {
ruvr_sets[[k]] <- RUVr(set, row.names(dge), k=k, res)
assay(reads_sce, paste("RUVr k=", toString(k))) <- log2(t(t(assayData(ruvr_sets[[k]])$normalizedCounts) / colSums(assayData(ruvr_sets[[k]])$normalizedCounts) * 1e6) + 1)
}
for(n in assayNames(reads_sce)) {
print(
plotPCA(
reads_sce,
colour_by = "Library.Generation.Set",
size_by = "total_features",
exprs_values = n
) + ggtitle(paste("Batch",n))
)
print(
plotPCA(
reads_sce,
colour_by = "Race",
shape_by = "Gender",
size_by = "total_features",
exprs_values = n
) + ggtitle(paste("Demographics", n))
)
}
non-plotting arguments like 'exprs_values' should go in 'run_args'
















Detect outliers
reads_sce <- runPCA(reads_sce, use_coldata = TRUE, detect_outliers = TRUE)
failed to find 'pct_counts_feature_control' in column metadatafailed to find 'total_features_by_counts_feature_control' in column metadatafailed to find 'log10_total_counts_endogenous' in column metadatafailed to find 'log10_total_counts_feature_control' in column metadata
outliers <- colnames(reads_sce)[reads_sce$outlier]
head(outliers)
character(0)
Examine sources of variance after removing unwanted variation
for (var in c("total_features", "Age", "Library.Generation.Set", "Index", "Participant.ID", "Race", "Gender", "Collection.Date")) {
print(
plotQC(reads_sce, type = "find-pcs", exprs_values = "RUVr k= 2", variable = var)
) + ggtitle(var)
}








Visualize top highly-expressed miRNAs male-vs-female


Visualize top highly-expressed miRNAs by Race
# Rank the mean expression values for plasma/serum miRs. Highest expression = 1
mean.expr.afr.rank <- rank(-1*rowMeans(norm.expr.matr[, colData(reads_sce)$Race=="African_American"]))
mean.expr.asn.rank <- rank(-1*rowMeans(norm.expr.matr[, colData(reads_sce)$Race=="Asian"]))
mean.expr.wht.rank <- rank(-1*rowMeans(norm.expr.matr[, colData(reads_sce)$Race=="White"]))
top_N <- 20
top.miRs <- row.names(norm.expr.matr)[mean.expr.afr.rank <= top_N | mean.expr.asn.rank <= top_N | mean.expr.wht.rank <= top_N] # get the names of the top miRs in plasma or serum
norm.expr.top <- norm.expr.matr[top.miRs, ] # Get the expression matrix for the top mIRs
norm.expr.melt <- reshape2::melt(norm.expr.top) # Convert your normalized expression matrix to a 3 column data.frame (row.name, col.name, expression value). Melt is in the dpylr package, I believe.
colnames(norm.expr.melt) <- c("miR.ID", "MT.Unique.ID", "norm.expr") # just so it's easier for me to tell you which columns I'm using.
norm.expr.melt$Race <- ""
for (row_num in 1:nrow(norm.expr.melt)){ # Pull the Gender values from the column metadata.
mt_unique_id <- norm.expr.melt[row_num, ]$MT.Unique.ID
norm.expr.melt[row_num, "Race"] <- as.character(samples[mt_unique_id,"Race"])
}
norm.expr.melt <- norm.expr.melt[norm.expr.melt$Race %in% c("African_American", "Asian", "White"),]
# This would be a simple boxplot with plasma/serum side-by-side. Overlaying the boxes over points takes a little more tweaking to get the dodge/width right, but it's doable.
ggplot(norm.expr.melt, aes(x=reorder(miR.ID, norm.expr, FUN="median"), y=norm.expr, fill=Race)) + geom_boxplot(pos="dodge", outlier.size=0.5) +
ggtitle("Top 20 Expressed miRNAs") + ylab("Normalized Expression") + xlab("miRNA ID") +
theme(panel.grid.major.x = element_line(size = 0.5, linetype = 'solid', colour = "grey"),
panel.grid.major.y = element_blank(),
axis.text.x = element_text(angle = 90, hjust = 1))

ggplot(norm.expr.melt, aes(x=reorder(miR.ID, norm.expr, FUN="median"), y=norm.expr, fill=Race)) + geom_boxplot(pos="dodge", outlier.size=0.5) +
ggtitle("Top 20 Expressed miRNAs") + ylab("Normalized Expression") + xlab("miRNA ID") +
theme(panel.grid.major.y = element_line(size = 0.5, linetype = 'solid', colour = "grey"),
panel.grid.major.x = element_blank()) + coord_flip()

Heatmap of most highly expressed miRNAs
library(pheatmap)
ruvr2 <- ruvr_sets[[2]]
norm.expr.matr <- norm.expr.matr[order(rowMeans(norm.expr.matr), decreasing=TRUE),]
Demographics <- pData(ruvr2)[,c("Age","Race", "Gender")]
annotations <- as.data.frame(Demographics)
rownames(annotations) <- rownames(pData(ruvr2))
pheatmap(norm.expr.matr[0:50,], cluster_rows=FALSE, show_rownames=TRUE, cluster_cols=TRUE, annotation_col=annotations, main="Top 50 Expressed miRNAs")

DE analysis with EdgeR
# based on Ryan's code, pass RUVSeq-corrected data to edgeR
ruvr2 <- ruvr_sets[[2]]
design <- model.matrix(~ 0 + Race + Gender + Age + W_1 + W_2, pData(ruvr2))
dge <- estimateDisp(dge, design = design, tagwise = TRUE, robust = TRUE)
fit <- glmFit(dge, design)
Core miRNAs stable across individuals (Fig2D)
ruvr_norm_matrix <- assayData(ruvr2)$normalizedCounts
#ruvr_norm_matrix <- counts
median <- apply(ruvr_norm_matrix, 1, median)
median_expr_df <- as.data.frame(median)
median_expr_df$qcd <- apply(ruvr_norm_matrix, 1, FUN=function(x){ q <- quantile(x, c(0.25, 0.75)); diff(q)/sum(q) }) # quartile coeff. of dispersion
median_expr_df$cv <- apply(ruvr_norm_matrix, 1, FUN=function(x){ 100*sd(x)/mean(x) }) # coefficient of variation
# log-2 transform the medians
median_expr_df$log2_median <- log(median_expr_df$median, 2)
# plot all miRNAs
ggplot(median_expr_df, aes(x=qcd, y=log2_median)) + geom_point() + ylab('Log2 Median Expression') + xlab('Quartile Coefficient of Dispersion') + ggtitle("miRNA QCD in Plasma")

ggplot(median_expr_df, aes(x=cv, y=log2_median)) + geom_point() + ylab('Log2 Median Expression') + xlab('Coefficient of Variation') + ggtitle("miRNA %CV in Plasma")

# plot only miRNAs expressed in 90% of samples
median_expr_df$sample_fraction = (rowSums(ruvr_norm_matrix > 0) / length(rownames(samples)))
sample_fraction_threshold = 0.9
median_expr_df_filt = median_expr_df[median_expr_df$sample_fraction >= sample_fraction_threshold, ]
ggplot(median_expr_df_filt, aes(x=qcd, y=log2_median)) + geom_point() + ylab('Log2 Median Expression') + xlab('Quartile Coefficient of Dispersion') + ggtitle("miRNA QCD in Plasma")

ggplot(median_expr_df_filt, aes(x=cv, y=log2_median)) + geom_point() + ylab('Log2 Median Expression') + xlab('Coefficient of Variation') + ggtitle("miRNA %CV in Plasma")

Contrast gender (Fig3B)
lrt_gender <- glmLRT(fit, coef = "GenderMale")
alpha_level <- 0.05
results_gender <- data.frame(topTags(lrt_gender, n=Inf, sort.by="PValue", p.value=alpha_level))
sig_miRs_gender = list()
logFC_threshold <- 1
sig_miRs_gender[[1]] <- rownames(results_gender[results_gender$PValue < alpha_level & results_gender$FDR < alpha_level & abs(results_gender$logFC) > logFC_threshold,]) # filter by p-value and logFC
sig_miRs_gender[[2]] <- rownames(results_gender[results_gender$PValue < alpha_level & results_gender$FDR < alpha_level,])
# heatmaps of significant DE miRNAs
Gender <- pData(ruvr2)[,c("Gender")]
annotations_gender <- as.data.frame(Gender)
rownames(annotations_gender) <- rownames(pData(ruvr2))
for(miR_list in sig_miRs_gender) {
pheatmap(norm.expr.matr[miR_list,], cluster_rows=TRUE, show_rownames=TRUE, cluster_cols=TRUE, annotation_col=annotations_gender, main="Differentially Expressed miRNAs")
}


# TODO: boxplots in one figure
sig_miRs_gender_table <- norm.expr.matr[sig_miRs_gender[[1]],]
sig_miRs_gender_table <- melt(sig_miRs_gender_table)
colnames(sig_miRs_gender_table) <- c("miRNA", "Sample", "Expression")
annotations_gender$Sample <- rownames(annotations_gender)
sig_miRs_gender_table <- merge(sig_miRs_gender_table, annotations_gender, by="Sample")
ggplot(data = sig_miRs_gender_table, aes(x=miRNA, y=Expression)) + geom_boxplot(aes(fill=Gender)) +
facet_wrap(~ miRNA, scales="free") +
ggtitle("Differentially Expressed miRNAs in Plasma by Gender")

Contrast race (Fig3B)
lrt_race <- glmLRT(fit, contrast=makeContrasts(asn=(RaceAsian-(RaceAfrican_American+RaceWhite)/2),
afr=(RaceAfrican_American-(RaceAsian+RaceWhite)/2),
wht=(RaceWhite-(RaceAfrican_American+RaceAsian)/2), levels=design))
results_race <- data.frame(topTags(lrt_race, n=Inf, sort.by="PValue", p.value=alpha_level))
sig_miRs_race_list = list()
sig_miRs_race_list[[1]] <- rownames(results_race[results_race$PValue < alpha_level & results_race$FDR < alpha_level & abs(results_race$logFC.asn) > logFC_threshold | abs(results_race$logFC.afr) > logFC_threshold | abs(results_race$logFC.wht) > logFC_threshold,]) # filter by p-value and logFC
sig_miRs_race_list[[2]] <- rownames(results_race[results_race$PValue < alpha_level & results_race$FDR < alpha_level,])
# heatmaps of significant DE miRNAs
Race <- pData(ruvr2)[,c("Race")]
annotations_race <- as.data.frame(Race)
rownames(annotations_race) <- rownames(pData(ruvr2))
for(miR_list in sig_miRs_race_list) {
pheatmap(norm.expr.matr[miR_list,], cluster_rows=TRUE, show_rownames=TRUE, cluster_cols=TRUE, annotation_col=annotations_race, main="Differentially Expressed miRNAs")
}


# boxplots of significant DE miRNAs
sig_miRs_race_table <- norm.expr.matr[sig_miRs_race_list[[1]],]
sig_miRs_race_table <- melt(sig_miRs_race_table)
colnames(sig_miRs_race_table) <- c("miRNA", "Sample", "Expression")
annotations_race$Sample <- rownames(annotations_race)
sig_miRs_race_table <- merge(sig_miRs_race_table, annotations_race, by="Sample")
sig_miRs_race_table <- sig_miRs_race_table[sig_miRs_race_table$Race != "Multiracial" & sig_miRs_race_table$Race != "Pacific_Islander",] # Pacific_Islander and Multiracial have too small sample sizes
ggplot(data = sig_miRs_race_table, aes(x=miRNA, y=Expression)) + geom_boxplot(aes(fill=Race)) +
facet_wrap(~ miRNA, scales="free") +
ggtitle("Differentially Expressed miRNAs in Plasma by Race")

Create a PCA plot showing Age x Gender
plotPCA(
reads_sce,
colour_by = "Age",
shape_by = "Gender",
size_by = "total_features",
exprs_values = "RUVr k= 2"
) + ggtitle("RUVSeq-Normalized Expression (k=2)")
non-plotting arguments like 'exprs_values' should go in 'run_args'

LS0tCnRpdGxlOiAibWlSTkEgRGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgZm9yIHBsYXNtYSBzYW1wbGVzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCiMgUGxhc21hIG9ubHkgc2FtcGxlcwpgYGB7cHl0aG9uM30KaW1wb3J0IHBhbmRhcyBhcyBwZApzYW1wbGVzID0gcGQucmVhZF9jc3YoJ2RhdGEvc2FtcGxlX3NoZWV0LmNzdicpCiMgbXVzdCBiZSBIZWFsdGh5IENvbnRyb2wgU3R1ZHkgYW5kIG11c3QgcGFzcyBNaVNlcSBRQwpzYW1wbGVzID0gc2FtcGxlcy5sb2NbKHNhbXBsZXNbJ1N0dWR5J10gPT0gJ0hlYWx0aHkgQ29udHJvbHMnKSAmIChzYW1wbGVzWydNSVNFUS5RQy5QQVNTJ10gPT0gJ1BBU1MnKV0gIApzYW1wbGVzID0gc2FtcGxlcy5zZXRfaW5kZXgoJ01ULlVuaXF1ZS5JRCcpLnNvcnRfdmFsdWVzKGJ5PVsnUGFydGljaXBhbnQuSUQnLCAnU291cmNlJ10pCiMgY2FwaXRhbGl6ZSAmIHN0cmlwIHdoaXRlc3BhY2UgZm9yIGNvbnNpc3RlbmN5CmZvciBjb2x1bW4gaW4gWydHZW5kZXInLCAnUmFjZScsICdTb3VyY2UnXToKICAgIHNhbXBsZXNbY29sdW1uXSA9IHNhbXBsZXNbY29sdW1uXS5zdHIuY2FwaXRhbGl6ZSgpCiAgICBzYW1wbGVzW2NvbHVtbl0gPSBzYW1wbGVzW2NvbHVtbl0uc3RyLnN0cmlwKCkKIyB1c2UgY29ycmVjdCBvbnRvbG9neSB0ZXJtcwpyYWNlX29udG9sb2d5ID0geydBc2lhbic6ICdBc2lhbicsCiAnQmxhY2sgb3IgYWZyaWNhbiBhbWVyaWNhbic6ICdBZnJpY2FuX0FtZXJpY2FuJywKICdNaXhlZC9hc2lhbiAmIHdoaXRlJzogJ011bHRpcmFjaWFsJywKICdNaXhlZC9hc2lhbiAmYmxhY2snOiAnTXVsdGlyYWNpYWwnLAogJ01peGVkL2JsYWNrLCB3aGl0ZSwgYXNpYW4nOiAnTXVsdGlyYWNpYWwnLAogJ05hdGl2ZSBoYXdpaWFuIG9yIG90aGVyIHBhY2lmaWMgaXNsYW5kZXInOiAnUGFjaWZpY19Jc2xhbmRlcicsCiAnUGFjaWZpYyBpc2xhbmRlcic6ICdQYWNpZmljX0lzbGFuZGVyJywKICdXaGl0ZSc6ICdXaGl0ZSd9CmZvciBpZCBpbiBzYW1wbGVzLmluZGV4OgogICAgcmFjZSA9IHNhbXBsZXMuYXRbaWQsICdSYWNlJ10KICAgIHNhbXBsZXMuYXRbaWQsICdSYWNlJ10gPSByYWNlX29udG9sb2d5W3JhY2VdIGlmIHJhY2UgaW4gcmFjZV9vbnRvbG9neSBlbHNlICdNdWx0aXJhY2lhbCcKIyBnZXQgb25seSB0aGUgcGxhc21hIHNhbXBsZXMKbWlyX2NvdW50cyA9IHBkLnJlYWRfY3N2KCJkYXRhL2dldF9jYW5vbmljYWwvY2Fub25fbWlyX2NvdW50cy5jc3YiLCBpbmRleF9jb2w9MCkKc2FtcGxlcy5pbmRleCA9IHBkLkluZGV4KFsnWCcgKyBzdHIocm93KSBmb3Igcm93IGluIHNhbXBsZXMuaW5kZXhdKQpwbGFzbWFfc2FtcGxlcyA9IHNhbXBsZXMubG9jW3NhbXBsZXNbJ1NvdXJjZSddID09ICJQbGFzbWEiXQpwbGFzbWFfbWlyX2NvdW50cyA9IG1pcl9jb3VudHNbcGxhc21hX3NhbXBsZXMuaW5kZXhdCnBsYXNtYV9zYW1wbGVzLnRvX2NzdignZGF0YS9wbGFzbWFfc2FtcGxlcy5jc3YnKQpwbGFzbWFfbWlyX2NvdW50cy50b19jc3YoJ2RhdGEvcGxhc21hX21pcl9jb3VudHMuY3N2JykKYGBgCgojIyBMb2FkIHRoZSBzYW1wbGUgZGF0YSBhbmQgbWlSTkEgY291bnRzCmBgYHtyfQpzYW1wbGVzIDwtIHJlYWQuY3N2KCdkYXRhL3BsYXNtYV9zYW1wbGVzLmNzdicpCmNvdW50cyA8LSByZWFkLmNzdignZGF0YS9wbGFzbWFfbWlyX2NvdW50cy5jc3YnKQpzYW1wbGVzIDwtIHN1YnNldChzYW1wbGVzLCBMaWJyYXJ5LkdlbmVyYXRpb24uU2V0ICE9ICJTZXRSZWNoZWNrIiApICAjIGV4Y2x1ZGUgU2V0UmVjaGVjayBzYW1wbGVzCnNhbXBsZXMgPC0gc2FtcGxlc1tzYW1wbGVzJFggIT0gIlgxMSIgJiBzYW1wbGVzJFggIT0gIlg5MiIsXSAjIHJlbW92ZSBvdXRsaWVycwpzYW1wbGVzJFBhcnRpY2lwYW50LklEIDwtIGZhY3RvcihzYW1wbGVzJFBhcnRpY2lwYW50LklEKSAgIyBJRCBpcyBjYXRlZ29yaWNhbCwgbm90IG51bWVyaWNhbApyb3duYW1lcyhjb3VudHMpID0gY291bnRzJFgKcm93bmFtZXMoc2FtcGxlcykgPSBzYW1wbGVzJFgKY291bnRzJFggPC0gTlVMTCAgIyByZW1vdmUgZXh0cmEgY29sdW1uCnNhbXBsZXMkWCA8LSBOVUxMCmNvdW50cyA8LSBjb3VudHNbLHVuaXF1ZShyb3duYW1lcyhzYW1wbGVzKSldCmhlYWQoc2FtcGxlcykKaGVhZChjb3VudHMpCmBgYAoKIyMgRmlsdGVyCmBgYHtyfQpsaWJyYXJ5KGVkZ2VSKQpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH4wK1JhY2UrR2VuZGVyK0FnZSwgc2FtcGxlcykKZGdlID0gREdFTGlzdChjb3VudHMgPSBjb3VudHMsIHNhbXBsZXMgPSBzYW1wbGVzKQojIHJlcXVpcmUgbWlSTkFzIHRvIGhhdmUgQ1BNID4gMSBpbiBhdCBsZWFzdCAyIHNhbXBsZXMKY291bnRzUGVyTWlsbGlvbiA8LSBlZGdlUjo6Y3BtKGRnZSkKY291bnRDaGVjayA8LSBjb3VudHNQZXJNaWxsaW9uID4gMQpoZWFkKGNvdW50Q2hlY2spCmtlZXAgPC0gd2hpY2gocm93U3Vtcyhjb3VudENoZWNrKSA+PSAyKSAKZGdlIDwtIGRnZVtrZWVwLF0KYGBgCiMjIEV4cGxvcmUgdmFyaWFuY2UKYGBge3J9CmxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCmxpYnJhcnkoc2NhdGVyKQpyZWFkc19zY2UgPC0gU2luZ2xlQ2VsbEV4cGVyaW1lbnQoYXNzYXlzPWxpc3QoY291bnRzPWRnZSRjb3VudHMpLCAgY29sRGF0YT1kZ2Ukc2FtcGxlcykKIyByZW1vdmUgdW5leHByZXNzZWQgbWlSTkFzCmtlZXBfZmVhdHVyZSA8LSByb3dTdW1zKGNvdW50cyhyZWFkc19zY2UpID4gMCkgPiAwCnJlYWRzX3NjZSA8LSByZWFkc19zY2Vba2VlcF9mZWF0dXJlLCBdCnJlYWRzX3NjZSA8LSBjYWxjdWxhdGVRQ01ldHJpY3MocmVhZHNfc2NlKQojIGxvZyB0cmFuc2Zvcm0KY3BtKHJlYWRzX3NjZSkgPC0gY2FsY3VsYXRlQ1BNKHJlYWRzX3NjZSkKcmVhZHNfc2NlIDwtIG5vcm1hbGl6ZShyZWFkc19zY2UpCmxvZ2NvdW50cyhyZWFkc19zY2UpIDwtIGxvZzIoY2FsY3VsYXRlQ1BNKHJlYWRzX3NjZSkgKyAxKQojIHZpc3VhbGl6ZQpoaXN0KHJlYWRzX3NjZSR0b3RhbF9jb3VudHMsIGJyZWFrcz0xMDApICAjIGNvdW50cyBwZXIgc2FtcGxlCmhpc3QocmVhZHNfc2NlJHRvdGFsX2ZlYXR1cmVzLCBicmVha3M9MTAwKSAgIyBjb3VudHMgcGVyIG1pUk5BCnBsb3RRQyhyZWFkc19zY2UsIHR5cGUgPSAiaGlnaGVzdC1leHByZXNzaW9uIikKcGxvdFFDKHJlYWRzX3NjZSwgdHlwZT0iZXhwbGFuYXRvcnktdmFyaWFibGVzIiwgdmFyaWFibGVzPWMoIkluZGV4IiwgIlBhcnRpY2lwYW50LklEIiwgIkNvbGxlY3Rpb24uRGF0ZSIsICJMaWJyYXJ5LkdlbmVyYXRpb24uU2V0IiwgIk1pU2VxLlFDLlJ1biIsICd0b3RhbF9mZWF0dXJlcycsICJBZ2UiLCAiUmFjZSIsICJHZW5kZXIiKSkKYGBgCiMjIEV4YW1pbmUgc291cmNlcyBvZiB2YXJpYXRpb24KYGBge3J9CnBsb3RQQ0EocmVhZHNfc2NlLCBleHByc192YWx1ZXMgPSAibG9nY291bnRzIiwgY29sb3VyX2J5ID0gIkxpYnJhcnkuR2VuZXJhdGlvbi5TZXQiLCBzaXplX2J5ID0gInRvdGFsX2ZlYXR1cmVzIikKcGxvdFBDQShyZWFkc19zY2UsIGV4cHJzX3ZhbHVlcyA9ICJsb2djb3VudHMiLCBjb2xvdXJfYnkgPSAiUmFjZSIsIHNoYXBlX2J5PSJHZW5kZXIiLCBzaXplX2J5ID0gInRvdGFsX2ZlYXR1cmVzIikKZm9yICh2YXIgaW4gYygidG90YWxfZmVhdHVyZXMiLCAiQWdlIiwgIkxpYnJhcnkuR2VuZXJhdGlvbi5TZXQiLCAiSW5kZXgiLCAiUGFydGljaXBhbnQuSUQiLCAiUmFjZSIsICJHZW5kZXIiLCAiQ29sbGVjdGlvbi5EYXRlIikpIHsKICBwcmludCgKICAgIHBsb3RRQyhyZWFkc19zY2UsIHR5cGUgPSAiZmluZC1wY3MiLCBleHByc192YWx1ZXMgPSAibG9nY291bnRzIiwgdmFyaWFibGUgPSB2YXIpCiAgICApICArIGdndGl0bGUodmFyKSAKICB9CmBgYAoKIyMgTm9ybWFsaXplICYgUmVtb3ZlIHVud2FudGVkIHNvdXJjZXMgb2YgdmFyaWF0aW9uCmBgYHtyfQpsaWJyYXJ5KFJVVlNlcSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KG12b3V0bGllcikKZGdlIDwtIGNhbGNOb3JtRmFjdG9ycyhkZ2UpCmRnZSA8LSBlc3RpbWF0ZUdMTUNvbW1vbkRpc3AoZGdlLCBkZXNpZ24pCmRnZSA8LSBlc3RpbWF0ZUdMTVRhZ3dpc2VEaXNwKGRnZSwgZGVzaWduKQpmaXQgPC0gZ2xtRml0KGRnZSwgZGVzaWduKQpyZXMgPC0gcmVzaWR1YWxzKGZpdCwgdHlwZT0iZGV2aWFuY2UiKQpzZXQgPC0gbmV3U2VxRXhwcmVzc2lvblNldChkZ2UkY291bnRzLCBwaGVub0RhdGE9ZGdlJHNhbXBsZXMpCnJ1dnJfc2V0cyA8LSBsaXN0KCkKZm9yKGsgaW4gMTo1KSB7CiAgcnV2cl9zZXRzW1trXV0gPC0gUlVWcihzZXQsIHJvdy5uYW1lcyhkZ2UpLCBrPWssIHJlcykKICBhc3NheShyZWFkc19zY2UsIHBhc3RlKCJSVVZyIGs9IiwgdG9TdHJpbmcoaykpKSA8LSBsb2cyKHQodChhc3NheURhdGEocnV2cl9zZXRzW1trXV0pJG5vcm1hbGl6ZWRDb3VudHMpIC8gY29sU3Vtcyhhc3NheURhdGEocnV2cl9zZXRzW1trXV0pJG5vcm1hbGl6ZWRDb3VudHMpICogMWU2KSArIDEpCn0KZm9yKG4gaW4gYXNzYXlOYW1lcyhyZWFkc19zY2UpKSB7CiAgcHJpbnQoCiAgICAgICAgcGxvdFBDQSgKICAgICAgICAgICAgcmVhZHNfc2NlLAogICAgICAgICAgICBjb2xvdXJfYnkgPSAiTGlicmFyeS5HZW5lcmF0aW9uLlNldCIsCiAgICAgICAgICAgIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLAogICAgICAgICAgICBleHByc192YWx1ZXMgPSBuCiAgICAgICAgKSArIGdndGl0bGUocGFzdGUoIkJhdGNoIixuKSkKICApCiAgcHJpbnQoCiAgICAgICAgcGxvdFBDQSgKICAgICAgICAgICAgcmVhZHNfc2NlLAogICAgICAgICAgICBjb2xvdXJfYnkgPSAiUmFjZSIsCiAgICAgICAgICAgIHNoYXBlX2J5ID0gIkdlbmRlciIsCiAgICAgICAgICAgIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLAogICAgICAgICAgICBleHByc192YWx1ZXMgPSBuCiAgICAgICAgKSArIGdndGl0bGUocGFzdGUoIkRlbW9ncmFwaGljcyIsIG4pKSAgICAgICAgCiAgKQp9CmBgYAoKIyMgRGV0ZWN0IG91dGxpZXJzCmBgYHtyfQpyZWFkc19zY2UgPC0gcnVuUENBKHJlYWRzX3NjZSwgdXNlX2NvbGRhdGEgPSBUUlVFLCBkZXRlY3Rfb3V0bGllcnMgPSBUUlVFKQpvdXRsaWVycyA8LSBjb2xuYW1lcyhyZWFkc19zY2UpW3JlYWRzX3NjZSRvdXRsaWVyXQpoZWFkKG91dGxpZXJzKQpgYGAKCiMjIEV4YW1pbmUgc291cmNlcyBvZiB2YXJpYW5jZSBhZnRlciByZW1vdmluZyB1bndhbnRlZCB2YXJpYXRpb24KYGBge3J9CmZvciAodmFyIGluIGMoInRvdGFsX2ZlYXR1cmVzIiwgIkFnZSIsICJMaWJyYXJ5LkdlbmVyYXRpb24uU2V0IiwgIkluZGV4IiwgIlBhcnRpY2lwYW50LklEIiwgIlJhY2UiLCAiR2VuZGVyIiwgIkNvbGxlY3Rpb24uRGF0ZSIpKSB7CiAgcHJpbnQoCiAgICBwbG90UUMocmVhZHNfc2NlLCB0eXBlID0gImZpbmQtcGNzIiwgZXhwcnNfdmFsdWVzID0gIlJVVnIgaz0gMiIsIHZhcmlhYmxlID0gdmFyKQogICAgKSAgKyBnZ3RpdGxlKHZhcikgCn0KYGBgCiMjIFZpc3VhbGl6ZSB0b3AgaGlnaGx5LWV4cHJlc3NlZCBtaVJOQXMgbWFsZS12cy1mZW1hbGUKYGBge3J9CmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KHJlc2hhcGUyKQojIFJ5YW4ncyBjb2RlLCBtb2RpZmllZApub3JtLmV4cHIubWF0ciA8LSBleHBycyhyZWFkc19zY2UpCiMgUmFuayB0aGUgbWVhbiBleHByZXNzaW9uIHZhbHVlcyBmb3IgcGxhc21hL3NlcnVtIG1pUnMuIEhpZ2hlc3QgZXhwcmVzc2lvbiA9IDEKbWVhbi5leHByLm1hbGUucmFuayA8LSByYW5rKC0xKnJvd01lYW5zKG5vcm0uZXhwci5tYXRyWywgY29sRGF0YShyZWFkc19zY2UpJEdlbmRlcj09Ik1hbGUiXSkpCm1lYW4uZXhwci5mZW1hbGUucmFuayA8LSByYW5rKC0xKnJvd01lYW5zKG5vcm0uZXhwci5tYXRyWywgY29sRGF0YShyZWFkc19zY2UpJEdlbmRlcj09IkZlbWFsZSJdKSkKdG9wX04gPC0gMjAKdG9wLm1pUnMgPC0gcm93Lm5hbWVzKG5vcm0uZXhwci5tYXRyKVttZWFuLmV4cHIubWFsZS5yYW5rIDw9IHRvcF9OIHwgbWVhbi5leHByLmZlbWFsZS5yYW5rIDw9IHRvcF9OXSAjIGdldCB0aGUgbmFtZXMgb2YgdGhlIHRvcCBtaVJzIGluIHBsYXNtYSBvciBzZXJ1bQpub3JtLmV4cHIudG9wIDwtIG5vcm0uZXhwci5tYXRyW3RvcC5taVJzLCBdICMgR2V0IHRoZSBleHByZXNzaW9uIG1hdHJpeCBmb3IgdGhlIHRvcCBtSVJzCm5vcm0uZXhwci5tZWx0IDwtIHJlc2hhcGUyOjptZWx0KG5vcm0uZXhwci50b3ApICMgQ29udmVydCB5b3VyIG5vcm1hbGl6ZWQgZXhwcmVzc2lvbiBtYXRyaXggdG8gYSAzIGNvbHVtbiBkYXRhLmZyYW1lIChyb3cubmFtZSwgY29sLm5hbWUsIGV4cHJlc3Npb24gdmFsdWUpLiBNZWx0IGlzIGluIHRoZSBkcHlsciBwYWNrYWdlLCBJIGJlbGlldmUuCmNvbG5hbWVzKG5vcm0uZXhwci5tZWx0KSA8LSBjKCJtaVIuSUQiLCAiTVQuVW5pcXVlLklEIiwgIm5vcm0uZXhwciIpICMganVzdCBzbyBpdCdzIGVhc2llciBmb3IgbWUgdG8gdGVsbCB5b3Ugd2hpY2ggY29sdW1ucyBJJ20gdXNpbmcuCm5vcm0uZXhwci5tZWx0JEdlbmRlciA8LSAiIgpmb3IgKHJvd19udW0gaW4gMTpucm93KG5vcm0uZXhwci5tZWx0KSl7ICAjIFB1bGwgdGhlIEdlbmRlciB2YWx1ZXMgZnJvbSB0aGUgY29sdW1uIG1ldGFkYXRhLgogIG10X3VuaXF1ZV9pZCA8LSBub3JtLmV4cHIubWVsdFtyb3dfbnVtLCBdJE1ULlVuaXF1ZS5JRAogIG5vcm0uZXhwci5tZWx0W3Jvd19udW0sICJHZW5kZXIiXSA8LSBhcy5jaGFyYWN0ZXIoc2FtcGxlc1ttdF91bmlxdWVfaWQsIkdlbmRlciJdKQp9CiMgVGhpcyB3b3VsZCBiZSBhIHNpbXBsZSBib3hwbG90IHdpdGggcGxhc21hL3NlcnVtIHNpZGUtYnktc2lkZS4gT3ZlcmxheWluZyB0aGUgYm94ZXMgb3ZlciBwb2ludHMgdGFrZXMgYSBsaXR0bGUgbW9yZSB0d2Vha2luZyB0byBnZXQgdGhlIGRvZGdlL3dpZHRoIHJpZ2h0LCBidXQgaXQncyBkb2FibGUuCmdncGxvdChub3JtLmV4cHIubWVsdCwgYWVzKHg9cmVvcmRlcihtaVIuSUQsIG5vcm0uZXhwciwgRlVOID0gIm1lZGlhbiIpLCB5PW5vcm0uZXhwciwgZmlsbD1HZW5kZXIpKSArIGdlb21fYm94cGxvdChwb3M9ImRvZGdlIiwgb3V0bGllci5zaXplPTAuNSkgKyAKICBnZ3RpdGxlKCJUb3AgMjAgRXhwcmVzc2VkIG1pUk5BcyIpICsgeWxhYigiTm9ybWFsaXplZCBFeHByZXNzaW9uIikgKyB4bGFiKCJtaVJOQSBJRCIpICsgCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9saW5lKHNpemUgPSAwLjUsIGxpbmV0eXBlID0gJ3NvbGlkJywgY29sb3VyID0gImdyZXkiKSwgCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpCmdncGxvdChub3JtLmV4cHIubWVsdCwgYWVzKHg9cmVvcmRlcihtaVIuSUQsIG5vcm0uZXhwciwgRlVOPSJtZWRpYW4iKSwgeT1ub3JtLmV4cHIsIGZpbGw9R2VuZGVyKSkgKyBnZW9tX2JveHBsb3QocG9zPSJkb2RnZSIsIG91dGxpZXIuc2l6ZT0wLjUpICsgCiAgZ2d0aXRsZSgiVG9wIDIwIEV4cHJlc3NlZCBtaVJOQXMiKSArIHlsYWIoIk5vcm1hbGl6ZWQgRXhwcmVzc2lvbiIpICsgeGxhYigibWlSTkEgSUQiKSArIAogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShzaXplID0gMC41LCBsaW5ldHlwZSA9ICdzb2xpZCcsIGNvbG91ciA9ICJncmV5IiksIAogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKyBjb29yZF9mbGlwKCkKYGBgCiMjIFZpc3VhbGl6ZSB0b3AgaGlnaGx5LWV4cHJlc3NlZCBtaVJOQXMgYnkgUmFjZQpgYGB7cn0KIyBSYW5rIHRoZSBtZWFuIGV4cHJlc3Npb24gdmFsdWVzIGZvciBwbGFzbWEvc2VydW0gbWlScy4gSGlnaGVzdCBleHByZXNzaW9uID0gMQptZWFuLmV4cHIuYWZyLnJhbmsgPC0gcmFuaygtMSpyb3dNZWFucyhub3JtLmV4cHIubWF0clssIGNvbERhdGEocmVhZHNfc2NlKSRSYWNlPT0iQWZyaWNhbl9BbWVyaWNhbiJdKSkKbWVhbi5leHByLmFzbi5yYW5rIDwtIHJhbmsoLTEqcm93TWVhbnMobm9ybS5leHByLm1hdHJbLCBjb2xEYXRhKHJlYWRzX3NjZSkkUmFjZT09IkFzaWFuIl0pKQptZWFuLmV4cHIud2h0LnJhbmsgPC0gcmFuaygtMSpyb3dNZWFucyhub3JtLmV4cHIubWF0clssIGNvbERhdGEocmVhZHNfc2NlKSRSYWNlPT0iV2hpdGUiXSkpCnRvcF9OIDwtIDIwCnRvcC5taVJzIDwtIHJvdy5uYW1lcyhub3JtLmV4cHIubWF0cilbbWVhbi5leHByLmFmci5yYW5rIDw9IHRvcF9OIHwgbWVhbi5leHByLmFzbi5yYW5rIDw9IHRvcF9OIHwgbWVhbi5leHByLndodC5yYW5rIDw9IHRvcF9OXSAjIGdldCB0aGUgbmFtZXMgb2YgdGhlIHRvcCBtaVJzIGluIHBsYXNtYSBvciBzZXJ1bQpub3JtLmV4cHIudG9wIDwtIG5vcm0uZXhwci5tYXRyW3RvcC5taVJzLCBdICMgR2V0IHRoZSBleHByZXNzaW9uIG1hdHJpeCBmb3IgdGhlIHRvcCBtSVJzCm5vcm0uZXhwci5tZWx0IDwtIHJlc2hhcGUyOjptZWx0KG5vcm0uZXhwci50b3ApICMgQ29udmVydCB5b3VyIG5vcm1hbGl6ZWQgZXhwcmVzc2lvbiBtYXRyaXggdG8gYSAzIGNvbHVtbiBkYXRhLmZyYW1lIChyb3cubmFtZSwgY29sLm5hbWUsIGV4cHJlc3Npb24gdmFsdWUpLiBNZWx0IGlzIGluIHRoZSBkcHlsciBwYWNrYWdlLCBJIGJlbGlldmUuCmNvbG5hbWVzKG5vcm0uZXhwci5tZWx0KSA8LSBjKCJtaVIuSUQiLCAiTVQuVW5pcXVlLklEIiwgIm5vcm0uZXhwciIpICMganVzdCBzbyBpdCdzIGVhc2llciBmb3IgbWUgdG8gdGVsbCB5b3Ugd2hpY2ggY29sdW1ucyBJJ20gdXNpbmcuCm5vcm0uZXhwci5tZWx0JFJhY2UgPC0gIiIKZm9yIChyb3dfbnVtIGluIDE6bnJvdyhub3JtLmV4cHIubWVsdCkpeyAgIyBQdWxsIHRoZSBHZW5kZXIgdmFsdWVzIGZyb20gdGhlIGNvbHVtbiBtZXRhZGF0YS4KICBtdF91bmlxdWVfaWQgPC0gbm9ybS5leHByLm1lbHRbcm93X251bSwgXSRNVC5VbmlxdWUuSUQKICBub3JtLmV4cHIubWVsdFtyb3dfbnVtLCAiUmFjZSJdIDwtIGFzLmNoYXJhY3RlcihzYW1wbGVzW210X3VuaXF1ZV9pZCwiUmFjZSJdKQp9Cm5vcm0uZXhwci5tZWx0IDwtIG5vcm0uZXhwci5tZWx0W25vcm0uZXhwci5tZWx0JFJhY2UgJWluJSBjKCJBZnJpY2FuX0FtZXJpY2FuIiwgIkFzaWFuIiwgIldoaXRlIiksXQojIFRoaXMgd291bGQgYmUgYSBzaW1wbGUgYm94cGxvdCB3aXRoIHBsYXNtYS9zZXJ1bSBzaWRlLWJ5LXNpZGUuIE92ZXJsYXlpbmcgdGhlIGJveGVzIG92ZXIgcG9pbnRzIHRha2VzIGEgbGl0dGxlIG1vcmUgdHdlYWtpbmcgdG8gZ2V0IHRoZSBkb2RnZS93aWR0aCByaWdodCwgYnV0IGl0J3MgZG9hYmxlLgpnZ3Bsb3Qobm9ybS5leHByLm1lbHQsIGFlcyh4PXJlb3JkZXIobWlSLklELCBub3JtLmV4cHIsIEZVTj0ibWVkaWFuIiksIHk9bm9ybS5leHByLCBmaWxsPVJhY2UpKSArIGdlb21fYm94cGxvdChwb3M9ImRvZGdlIiwgb3V0bGllci5zaXplPTAuNSkgKyAKICBnZ3RpdGxlKCJUb3AgMjAgRXhwcmVzc2VkIG1pUk5BcyIpICsgeWxhYigiTm9ybWFsaXplZCBFeHByZXNzaW9uIikgKyB4bGFiKCJtaVJOQSBJRCIpICsgCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9saW5lKHNpemUgPSAwLjUsIGxpbmV0eXBlID0gJ3NvbGlkJywgY29sb3VyID0gImdyZXkiKSwgCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpCmdncGxvdChub3JtLmV4cHIubWVsdCwgYWVzKHg9cmVvcmRlcihtaVIuSUQsIG5vcm0uZXhwciwgRlVOPSJtZWRpYW4iKSwgeT1ub3JtLmV4cHIsIGZpbGw9UmFjZSkpICsgZ2VvbV9ib3hwbG90KHBvcz0iZG9kZ2UiLCBvdXRsaWVyLnNpemU9MC41KSArIAogIGdndGl0bGUoIlRvcCAyMCBFeHByZXNzZWQgbWlSTkFzIikgKyB5bGFiKCJOb3JtYWxpemVkIEV4cHJlc3Npb24iKSArIHhsYWIoIm1pUk5BIElEIikgKyAKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IDAuNSwgbGluZXR5cGUgPSAnc29saWQnLCBjb2xvdXIgPSAiZ3JleSIpLCAKICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsgY29vcmRfZmxpcCgpCmBgYAoKIyMgSGVhdG1hcCBvZiBtb3N0IGhpZ2hseSBleHByZXNzZWQgbWlSTkFzCmBgYHtyfQpsaWJyYXJ5KHBoZWF0bWFwKQpydXZyMiA8LSBydXZyX3NldHNbWzJdXQpub3JtLmV4cHIubWF0ciA8LSBub3JtLmV4cHIubWF0cltvcmRlcihyb3dNZWFucyhub3JtLmV4cHIubWF0ciksIGRlY3JlYXNpbmc9VFJVRSksXQpEZW1vZ3JhcGhpY3MgPC0gcERhdGEocnV2cjIpWyxjKCJBZ2UiLCJSYWNlIiwgIkdlbmRlciIpXQphbm5vdGF0aW9ucyA8LSBhcy5kYXRhLmZyYW1lKERlbW9ncmFwaGljcykKcm93bmFtZXMoYW5ub3RhdGlvbnMpIDwtIHJvd25hbWVzKHBEYXRhKHJ1dnIyKSkKcGhlYXRtYXAobm9ybS5leHByLm1hdHJbMDo1MCxdLCBjbHVzdGVyX3Jvd3M9RkFMU0UsIHNob3dfcm93bmFtZXM9VFJVRSwgY2x1c3Rlcl9jb2xzPVRSVUUsIGFubm90YXRpb25fY29sPWFubm90YXRpb25zLCBtYWluPSJUb3AgNTAgRXhwcmVzc2VkIG1pUk5BcyIpCmBgYAoKIyBERSBhbmFseXNpcyB3aXRoIEVkZ2VSCmBgYHtyfQojIGJhc2VkIG9uIFJ5YW4ncyBjb2RlLCBwYXNzIFJVVlNlcS1jb3JyZWN0ZWQgZGF0YSB0byBlZGdlUgpydXZyMiA8LSBydXZyX3NldHNbWzJdXQpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH4gMCArIFJhY2UgKyBHZW5kZXIgKyBBZ2UgKyBXXzEgKyBXXzIsIHBEYXRhKHJ1dnIyKSkKZGdlIDwtIGVzdGltYXRlRGlzcChkZ2UsIGRlc2lnbiA9IGRlc2lnbiwgdGFnd2lzZSA9IFRSVUUsIHJvYnVzdCA9IFRSVUUpCmZpdCA8LSBnbG1GaXQoZGdlLCBkZXNpZ24pCmBgYAoKIyMgQ29yZSBtaVJOQXMgc3RhYmxlIGFjcm9zcyBpbmRpdmlkdWFscyAoRmlnMkQpCmBgYHtyfQpydXZyX25vcm1fbWF0cml4IDwtIGFzc2F5RGF0YShydXZyMikkbm9ybWFsaXplZENvdW50cwojcnV2cl9ub3JtX21hdHJpeCA8LSBjb3VudHMKbWVkaWFuIDwtIGFwcGx5KHJ1dnJfbm9ybV9tYXRyaXgsIDEsIG1lZGlhbikKbWVkaWFuX2V4cHJfZGYgPC0gYXMuZGF0YS5mcmFtZShtZWRpYW4pCm1lZGlhbl9leHByX2RmJHFjZCA8LSBhcHBseShydXZyX25vcm1fbWF0cml4LCAxLCBGVU49ZnVuY3Rpb24oeCl7IHEgPC0gcXVhbnRpbGUoeCwgYygwLjI1LCAwLjc1KSk7IGRpZmYocSkvc3VtKHEpIH0pICAjIHF1YXJ0aWxlIGNvZWZmLiBvZiBkaXNwZXJzaW9uCm1lZGlhbl9leHByX2RmJGN2IDwtIGFwcGx5KHJ1dnJfbm9ybV9tYXRyaXgsIDEsIEZVTj1mdW5jdGlvbih4KXsgMTAwKnNkKHgpL21lYW4oeCkgfSkgICMgY29lZmZpY2llbnQgb2YgdmFyaWF0aW9uCiMgbG9nLTIgdHJhbnNmb3JtIHRoZSBtZWRpYW5zCm1lZGlhbl9leHByX2RmJGxvZzJfbWVkaWFuIDwtIGxvZyhtZWRpYW5fZXhwcl9kZiRtZWRpYW4sIDIpCiMgcGxvdCBhbGwgbWlSTkFzCmdncGxvdChtZWRpYW5fZXhwcl9kZiwgYWVzKHg9cWNkLCB5PWxvZzJfbWVkaWFuKSkgKyBnZW9tX3BvaW50KCkgKyB5bGFiKCdMb2cyIE1lZGlhbiBFeHByZXNzaW9uJykgKyB4bGFiKCdRdWFydGlsZSBDb2VmZmljaWVudCBvZiBEaXNwZXJzaW9uJykgKyBnZ3RpdGxlKCJtaVJOQSBRQ0QgaW4gUGxhc21hIikKZ2dwbG90KG1lZGlhbl9leHByX2RmLCBhZXMoeD1jdiwgeT1sb2cyX21lZGlhbikpICsgZ2VvbV9wb2ludCgpICsgeWxhYignTG9nMiBNZWRpYW4gRXhwcmVzc2lvbicpICsgeGxhYignQ29lZmZpY2llbnQgb2YgVmFyaWF0aW9uJykgKyBnZ3RpdGxlKCJtaVJOQSAlQ1YgaW4gUGxhc21hIikKIyBwbG90IG9ubHkgbWlSTkFzIGV4cHJlc3NlZCBpbiA5MCUgb2Ygc2FtcGxlcwptZWRpYW5fZXhwcl9kZiRzYW1wbGVfZnJhY3Rpb24gPSAocm93U3VtcyhydXZyX25vcm1fbWF0cml4ID4gMCkgLyBsZW5ndGgocm93bmFtZXMoc2FtcGxlcykpKQpzYW1wbGVfZnJhY3Rpb25fdGhyZXNob2xkID0gMC45Cm1lZGlhbl9leHByX2RmX2ZpbHQgPSBtZWRpYW5fZXhwcl9kZlttZWRpYW5fZXhwcl9kZiRzYW1wbGVfZnJhY3Rpb24gPj0gc2FtcGxlX2ZyYWN0aW9uX3RocmVzaG9sZCwgXQpnZ3Bsb3QobWVkaWFuX2V4cHJfZGZfZmlsdCwgYWVzKHg9cWNkLCB5PWxvZzJfbWVkaWFuKSkgKyBnZW9tX3BvaW50KCkgKyB5bGFiKCdMb2cyIE1lZGlhbiBFeHByZXNzaW9uJykgKyB4bGFiKCdRdWFydGlsZSBDb2VmZmljaWVudCBvZiBEaXNwZXJzaW9uJykgKyBnZ3RpdGxlKCJtaVJOQSBRQ0QgaW4gUGxhc21hIikKZ2dwbG90KG1lZGlhbl9leHByX2RmX2ZpbHQsIGFlcyh4PWN2LCB5PWxvZzJfbWVkaWFuKSkgKyBnZW9tX3BvaW50KCkgKyB5bGFiKCdMb2cyIE1lZGlhbiBFeHByZXNzaW9uJykgKyB4bGFiKCdDb2VmZmljaWVudCBvZiBWYXJpYXRpb24nKSArIGdndGl0bGUoIm1pUk5BICVDViBpbiBQbGFzbWEiKQpgYGAKCiMjIENvbnRyYXN0IGdlbmRlciAoRmlnM0IpCmBgYHtyfQpscnRfZ2VuZGVyIDwtIGdsbUxSVChmaXQsIGNvZWYgPSAiR2VuZGVyTWFsZSIpCmFscGhhX2xldmVsIDwtIDAuMDUKcmVzdWx0c19nZW5kZXIgPC0gZGF0YS5mcmFtZSh0b3BUYWdzKGxydF9nZW5kZXIsIG49SW5mLCBzb3J0LmJ5PSJQVmFsdWUiLCBwLnZhbHVlPWFscGhhX2xldmVsKSkKc2lnX21pUnNfZ2VuZGVyX2xpc3QgPSBsaXN0KCkKbG9nRkNfdGhyZXNob2xkIDwtIDEKc2lnX21pUnNfZ2VuZGVyX2xpc3RbWzFdXSA8LSByb3duYW1lcyhyZXN1bHRzX2dlbmRlcltyZXN1bHRzX2dlbmRlciRQVmFsdWUgPCBhbHBoYV9sZXZlbCAmIHJlc3VsdHNfZ2VuZGVyJEZEUiA8IGFscGhhX2xldmVsICYgYWJzKHJlc3VsdHNfZ2VuZGVyJGxvZ0ZDKSA+IGxvZ0ZDX3RocmVzaG9sZCxdKSAgIyBmaWx0ZXIgYnkgcC12YWx1ZSBhbmQgbG9nRkMKc2lnX21pUnNfZ2VuZGVyX2xpc3RbWzJdXSA8LSByb3duYW1lcyhyZXN1bHRzX2dlbmRlcltyZXN1bHRzX2dlbmRlciRQVmFsdWUgPCBhbHBoYV9sZXZlbCAmIHJlc3VsdHNfZ2VuZGVyJEZEUiA8IGFscGhhX2xldmVsLF0pCiMgaGVhdG1hcHMgb2Ygc2lnbmlmaWNhbnQgREUgbWlSTkFzCkdlbmRlciA8LSBwRGF0YShydXZyMilbLGMoIkdlbmRlciIpXQphbm5vdGF0aW9uc19nZW5kZXIgPC0gYXMuZGF0YS5mcmFtZShHZW5kZXIpCnJvd25hbWVzKGFubm90YXRpb25zX2dlbmRlcikgPC0gcm93bmFtZXMocERhdGEocnV2cjIpKQpmb3IobWlSX2xpc3QgaW4gc2lnX21pUnNfZ2VuZGVyX2xpc3QpIHsKICBwaGVhdG1hcChub3JtLmV4cHIubWF0clttaVJfbGlzdCxdLCBjbHVzdGVyX3Jvd3M9VFJVRSwgc2hvd19yb3duYW1lcz1UUlVFLCBjbHVzdGVyX2NvbHM9VFJVRSwgYW5ub3RhdGlvbl9jb2w9YW5ub3RhdGlvbnNfZ2VuZGVyLCBtYWluPSJEaWZmZXJlbnRpYWxseSBFeHByZXNzZWQgbWlSTkFzIikKfQojIGJveHBsb3RzIG9mIHNpZ25pZmljYW50IERFIG1pUk5BcwpzaWdfbWlSc19nZW5kZXJfdGFibGUgPC0gbm9ybS5leHByLm1hdHJbc2lnX21pUnNfZ2VuZGVyX2xpc3RbWzFdXSxdCnNpZ19taVJzX2dlbmRlcl90YWJsZSA8LSBtZWx0KHNpZ19taVJzX2dlbmRlcl90YWJsZSkKY29sbmFtZXMoc2lnX21pUnNfZ2VuZGVyX3RhYmxlKSA8LSBjKCJtaVJOQSIsICJTYW1wbGUiLCAiRXhwcmVzc2lvbiIpCmFubm90YXRpb25zX2dlbmRlciRTYW1wbGUgPC0gcm93bmFtZXMoYW5ub3RhdGlvbnNfZ2VuZGVyKQpzaWdfbWlSc19nZW5kZXJfdGFibGUgPC0gbWVyZ2Uoc2lnX21pUnNfZ2VuZGVyX3RhYmxlLCBhbm5vdGF0aW9uc19nZW5kZXIsIGJ5PSJTYW1wbGUiKQpnZ3Bsb3QoZGF0YSA9IHNpZ19taVJzX2dlbmRlcl90YWJsZSwgYWVzKHg9bWlSTkEsIHk9RXhwcmVzc2lvbikpICsgZ2VvbV9ib3hwbG90KGFlcyhmaWxsPUdlbmRlcikpICsgCiAgZmFjZXRfd3JhcCh+IG1pUk5BLCBzY2FsZXM9ImZyZWUiKSArIAogIGdndGl0bGUoIkRpZmZlcmVudGlhbGx5IEV4cHJlc3NlZCBtaVJOQXMgaW4gUGxhc21hIGJ5IEdlbmRlciIpCmBgYAoKIyMgQ29udHJhc3QgcmFjZSAoRmlnM0IpCmBgYHtyfQpscnRfcmFjZSA8LSBnbG1MUlQoZml0LCBjb250cmFzdD1tYWtlQ29udHJhc3RzKGFzbj0oUmFjZUFzaWFuLShSYWNlQWZyaWNhbl9BbWVyaWNhbitSYWNlV2hpdGUpLzIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZnI9KFJhY2VBZnJpY2FuX0FtZXJpY2FuLShSYWNlQXNpYW4rUmFjZVdoaXRlKS8yKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2h0PShSYWNlV2hpdGUtKFJhY2VBZnJpY2FuX0FtZXJpY2FuK1JhY2VBc2lhbikvMiksIGxldmVscz1kZXNpZ24pKQpyZXN1bHRzX3JhY2UgPC0gZGF0YS5mcmFtZSh0b3BUYWdzKGxydF9yYWNlLCBuPUluZiwgc29ydC5ieT0iUFZhbHVlIiwgcC52YWx1ZT1hbHBoYV9sZXZlbCkpCnNpZ19taVJzX3JhY2VfbGlzdCA9IGxpc3QoKQpzaWdfbWlSc19yYWNlX2xpc3RbWzFdXSA8LSByb3duYW1lcyhyZXN1bHRzX3JhY2VbcmVzdWx0c19yYWNlJFBWYWx1ZSA8IGFscGhhX2xldmVsICYgcmVzdWx0c19yYWNlJEZEUiA8IGFscGhhX2xldmVsICYgYWJzKHJlc3VsdHNfcmFjZSRsb2dGQy5hc24pID4gbG9nRkNfdGhyZXNob2xkIHwgYWJzKHJlc3VsdHNfcmFjZSRsb2dGQy5hZnIpID4gbG9nRkNfdGhyZXNob2xkIHwgYWJzKHJlc3VsdHNfcmFjZSRsb2dGQy53aHQpID4gbG9nRkNfdGhyZXNob2xkLF0pICAjIGZpbHRlciBieSBwLXZhbHVlIGFuZCBsb2dGQwpzaWdfbWlSc19yYWNlX2xpc3RbWzJdXSA8LSByb3duYW1lcyhyZXN1bHRzX3JhY2VbcmVzdWx0c19yYWNlJFBWYWx1ZSA8IGFscGhhX2xldmVsICYgcmVzdWx0c19yYWNlJEZEUiA8IGFscGhhX2xldmVsLF0pCiMgaGVhdG1hcHMgb2Ygc2lnbmlmaWNhbnQgREUgbWlSTkFzClJhY2UgPC0gcERhdGEocnV2cjIpWyxjKCJSYWNlIildCmFubm90YXRpb25zX3JhY2UgPC0gYXMuZGF0YS5mcmFtZShSYWNlKQpyb3duYW1lcyhhbm5vdGF0aW9uc19yYWNlKSA8LSByb3duYW1lcyhwRGF0YShydXZyMikpCmZvcihtaVJfbGlzdCBpbiBzaWdfbWlSc19yYWNlX2xpc3QpIHsKICBwaGVhdG1hcChub3JtLmV4cHIubWF0clttaVJfbGlzdCxdLCBjbHVzdGVyX3Jvd3M9VFJVRSwgc2hvd19yb3duYW1lcz1UUlVFLCBjbHVzdGVyX2NvbHM9VFJVRSwgYW5ub3RhdGlvbl9jb2w9YW5ub3RhdGlvbnNfcmFjZSwgbWFpbj0iRGlmZmVyZW50aWFsbHkgRXhwcmVzc2VkIG1pUk5BcyIpCn0KIyBib3hwbG90cyBvZiBzaWduaWZpY2FudCBERSBtaVJOQXMKc2lnX21pUnNfcmFjZV90YWJsZSA8LSBub3JtLmV4cHIubWF0cltzaWdfbWlSc19yYWNlX2xpc3RbWzFdXSxdCnNpZ19taVJzX3JhY2VfdGFibGUgPC0gbWVsdChzaWdfbWlSc19yYWNlX3RhYmxlKQpjb2xuYW1lcyhzaWdfbWlSc19yYWNlX3RhYmxlKSA8LSBjKCJtaVJOQSIsICJTYW1wbGUiLCAiRXhwcmVzc2lvbiIpCmFubm90YXRpb25zX3JhY2UkU2FtcGxlIDwtIHJvd25hbWVzKGFubm90YXRpb25zX3JhY2UpCnNpZ19taVJzX3JhY2VfdGFibGUgPC0gbWVyZ2Uoc2lnX21pUnNfcmFjZV90YWJsZSwgYW5ub3RhdGlvbnNfcmFjZSwgYnk9IlNhbXBsZSIpCnNpZ19taVJzX3JhY2VfdGFibGUgPC0gc2lnX21pUnNfcmFjZV90YWJsZVtzaWdfbWlSc19yYWNlX3RhYmxlJFJhY2UgIT0gIk11bHRpcmFjaWFsIiAmIHNpZ19taVJzX3JhY2VfdGFibGUkUmFjZSAhPSAiUGFjaWZpY19Jc2xhbmRlciIsXSAjIFBhY2lmaWNfSXNsYW5kZXIgYW5kIE11bHRpcmFjaWFsIGhhdmUgdG9vIHNtYWxsIHNhbXBsZSBzaXplcwpnZ3Bsb3QoZGF0YSA9IHNpZ19taVJzX3JhY2VfdGFibGUsIGFlcyh4PW1pUk5BLCB5PUV4cHJlc3Npb24pKSArIGdlb21fYm94cGxvdChhZXMoZmlsbD1SYWNlKSkgKyAKICBmYWNldF93cmFwKH4gbWlSTkEsIHNjYWxlcz0iZnJlZSIpICsgCiAgZ2d0aXRsZSgiRGlmZmVyZW50aWFsbHkgRXhwcmVzc2VkIG1pUk5BcyBpbiBQbGFzbWEgYnkgUmFjZSIpCmBgYAoKIyMgQ3JlYXRlIGEgUENBIHBsb3Qgc2hvd2luZyBBZ2UgeCBHZW5kZXIKYGBge3J9CnBsb3RQQ0EoCiAgICAgICAgICAgIHJlYWRzX3NjZSwKICAgICAgICAgICAgY29sb3VyX2J5ID0gIkFnZSIsCiAgICAgICAgICAgIHNoYXBlX2J5ID0gIkdlbmRlciIsCiAgICAgICAgICAgIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLAogICAgICAgICAgICBleHByc192YWx1ZXMgPSAiUlVWciBrPSAyIgopICsgZ2d0aXRsZSgiUlVWU2VxLU5vcm1hbGl6ZWQgRXhwcmVzc2lvbiAoaz0yKSIpIApgYGAKCmBgYHtyfQpzYXZlLmltYWdlKCJkaWZmX2V4cHJfZGVtb2dyYXBoaWNzX3BsYXNtYV9vbmx5LlJEYXRhIikKYGBg